/**
 * Copyright 2009 - ChunMap
 * 
 * 这个文件是ChunMap项目的一部分。
 * ChunMap是开源软件。
 * 你可以自由复制、传播本项目的下载包文件，但必须保持其完整性。
 * 我们不用对使用中的风险及由此造成的损失承担任何责任。
 * 详细情况请见《ChunMap许可协议》。

 * 想了解更多有关ChunMap的信息，请访问http://code.google.com/p/chunmap/
 */
package chunmap.orm;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import chunmap.orm.connect.DBUtils;
import chunmap.orm.mapping.Mapping;
import chunmap.orm.mapping.Table;
import chunmap.orm.sql.Sql;
import chunmap.orm.sql.SqlMaker;
import chunmap.orm.sql.SqlMakerFactory;
import chunmap.util.ChunDaoException;
import chunmap.util.Function;
import chunmap.util.Logger;
import chunmap.util.Result;

public class DefaultDataSession implements DataSession {
	private static final Logger Log = new Logger(Logger.Info,
			DefaultDataSession.class.getName());

	public Mapping mapping;
	private Connection conn;
	private SqlMaker sqlMaker;

	public DefaultDataSession(Connection conn, Mapping mapping,
			int databaseProduct) {
		super();
		this.conn = conn;
		this.mapping = mapping;
		this.sqlMaker = SqlMakerFactory.getSqlMaker(databaseProduct);
	}

	@Override
	public void begin() throws SQLException {
		conn.setAutoCommit(false);
	}

	@Override
	public void commit() throws SQLException {
		conn.commit();
		conn.setAutoCommit(true);
	}

	@Override
	public void rollback() throws SQLException {
		conn.rollback();
	}

	@Override
	public Connection getConn() {
		return conn;
	}

	@Override
	public Object countWhere(Class<?> type, String count, Object condition) {
		Sql sql = getSqlMaker(type).countWhere(count, condition.toString());
		final Result result = new Result();
		sql.execute(getConn(), readFirstObjectCallback(result));
		return result.getValue();
	}

	private Function readFirstObjectCallback(final Result result) {
		return new Function() {
			@Override
			public Object call(Object... args) {
				ResultSet rs = (ResultSet) args[0];
				Object obj;
				try {
					rs.next();
					obj = rs.getObject(1);
				} catch (SQLException e) {
					throw new ChunDaoException(e);
				}
				result.setValue(obj);
				return null;
			}
		};
	}

	private Function getLineNumCallback(final Result result) {
		return new Function() {
			@Override
			public Object call(Object... args) {
				Integer n = (Integer) args[0];
				result.setValue(n);
				return null;
			}
		};
	}

	@Override
	public void delete(Object obj) {
		getSqlMaker(obj.getClass()).delete(obj).execute(getConn(), null);
	}

	@Override
	public int deleteWhere(Class<?> type, Object condition) {
		final Result result = new Result();
		getSqlMaker(type).deleteWhere(condition.toString()).execute(getConn(),
				getLineNumCallback(result));
		return (Integer) result.getValue();
	}

	@Override
	public boolean existById(Class<?> type, Object id) {
		final Result result = new Result();
		getSqlMaker(type).selectById(id).execute(getConn(),
				readFirstObjectCallback(result));
		int n = (Integer) result.getValue();
		return n > 0;
	}

	@Override
	public void insert(Object obj) {
		getSqlMaker(obj.getClass()).insert(obj).execute(getConn(), null);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T selectById(Class<T> type, Object id) {
		final Result result = new Result();
		final SqlMaker sqlMaker = getSqlMaker(type);
		sqlMaker.selectById(id).execute(getConn(), new Function() {
			@Override
			public Object call(Object... args) {
				ResultSet rs = (ResultSet) args[0];
				Object obj = sqlMaker.getTable().readFirstObject(rs);
				result.setValue(obj);
				return null;
			}
		});

		T r = (T) result.getValue();
		onLoadFinished(r);
		return r;
	}

	private void onLoadFinished(Object obj) {
		if (obj instanceof ObjectLoadEvent) {
			ObjectLoadEvent.class.cast(obj).buildFinished(this);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> List<T> selectWhere(Class<T> type, Object condition,
			final Page page) {
		final Result result = new Result();
		final SqlMaker sqlMaker = getSqlMaker(type);
		sqlMaker.selectWhere(condition.toString()).execute(getConn(),
				new Function() {
					@Override
					public Object call(Object... args) {
						ResultSet rs = (ResultSet) args[0];
						Object obj = null;

						if (page == null) {
							obj = sqlMaker.getTable().readAllObject(rs);
						} else {
							obj = sqlMaker.getTable().readPageObject(rs,
									page.getStartNum(), page.getPageSize());
						}

						result.setValue(obj);
						return null;
					}
				});

		List<T> list = (List<T>) result.getValue();
		for (T r : list) {
			onLoadFinished(r);
		}
		return list;
	}

	@Override
	public void update(Object obj) {
		getSqlMaker(obj.getClass()).update(obj).execute(getConn(), null);
	}

	@Override
	public int updateWhere(Object condition, Object obj, Function filter) {
		final Result result = new Result();
		getSqlMaker(obj.getClass()).updateWhere(condition.toString(), obj,
				filter).execute(getConn(), getLineNumCallback(result));
		return (Integer) result.getValue();
	}

	private SqlMaker getSqlMaker(Class<?> type) {
		Table table = mapping.findTable(type);
		sqlMaker.setTable(table);
		return sqlMaker;
	}

	@Override
	public void createTable(Class<?> type) {
		getSqlMaker(type).createTable().execute(getConn(), null);
	}

	@Override
	public void dropTable(Class<?> type) {
		SqlMaker sqlMaker = getSqlMaker(type);

		// if already dropped
		String tableName = sqlMaker.getTable().getName();
		boolean isExist = DBUtils.hasTable(getConn(), tableName);
		if (!isExist) {
			Log.log(Logger.Warn, tableName + " already droped");
			return;
		}

		sqlMaker.dropTable().execute(getConn(), null);
	}

	@Override
	public void rebuildDatabase() {
		mapping.each(new Function() {
			@Override
			public Object call(Object... args) {
				Table table = (Table) args[0];
				dropTable(table.getType());
				createTable(table.getType());
				return null;
			}
		});
	}

	@Override
	public void close() {
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}

	@Override
	public void executeSql(Sql sql, Function callback) {
		sql.execute(getConn(), callback);
	}

	@Override
	public void query(Class<?> type, String condition, final Function callback) {
		final SqlMaker sqlMaker = getSqlMaker(type);
		sqlMaker.selectWhere(condition).execute(getConn(), new Function() {
			@Override
			public Object call(Object... args) {
				ResultSet rs = (ResultSet) args[0];
				try {
					while (rs.next()) {
						Object obj = sqlMaker.getTable().readObject(rs);
						onLoadFinished(obj);
						callback.call(obj);
					}
				} catch (SQLException e) {
					try {
						rs.close();
					} catch (SQLException e1) {
						e1.printStackTrace();
					}
				}
				return null;
			}
		});
	}

}
